<?php
/**
* 名称：PDO扩展类。
* 描述：继承自PDO类，包含原PDO类所有功能，并针对数据库常用扩展相关功能。
*/
final class EPDO extends PDO{
    /**
    * 描述：工作模式。
    */
    const MODE='silent';

    /**
    * 描述：存储数据库名称 。
    */
    private $DatabaseName=NULL;

    /**
    * 描述：用于EPDO类的扩展属性，须在实例化时做为配置参数传入。
    */
    private $ExtentAttribute=NULL;

    /**
    * 描述：保存EPDO扩展方法执行后的错误描述信息。
    */
    private $ExtensionMethodErrorInfo=NULL;

    /**
    * 描述：EPDO构造函数，参数以数组形式传入。
    * @param  string $config['DSN']             必选 服务连接信息。
    * @param  string $config['User']            必选 用户名。
    * @param  string $config['Password']        必选 密码。
    * @param  array  $config['Options']         可选 数据源配置选项。
    * @param  array  $config['Attribute']       可选 PDO原生属性设置。
    * @param  array  $config['ExtendAttribute'] 可选 EPDO属性设置。
    * @return void
    */
    public function __Construct($config,$exceptionObject=NULL){
        if(self::MODE==='silent'){
            $temp=ini_get('error_reporting');
            try{
                error_reporting(0);
                parent::__construct($config['DSN'],$config['User'],$config['Password'],$config['Options']);
                error_reporting($temp);
            }catch (Exception $exception){
                error_reporting($temp);
                if($exceptionObject&&gettype($exceptionObject==='object')&&method_exists($exceptionObject,'InjectException')){
                    $exceptionObject->injectException($exception);
                }
                return;
            }
        }
        else{
            parent::__construct($config['DSN'],$config['User'],$config['Password'],$config['Options']);
        }
        foreach($config['Attribute'] as $key=>$value){
            $this->SetAttribute($key,$value);
        }
        $this->ExtentAttribute=$config['ExtendAttribute'];
        //-
        foreach(explode(';',$config['DSN']) as $value){
            $temp=explode('=',$value);
            if('dbname'===$temp[0]){
                $this->DatabaseName=$temp[1];
                break;
            }
        }
    }

    /**
    * 描述：返回完整的数据库及表名字符串。
    * @param  string $tableName 必选 数据表名。
    * @return string
    */
    public function GetOne($sql,$arguments=array()){
        $pdoStatement=$this->Prepare($sql.' LIMIT 0,1');
        $this->BoundParameters($pdoStatement,$arguments);
        $data=$this->ExecuteHandler($pdoStatement,'fetch',PDO::FETCH_NUM);
        if($data){
            return $data[0][0];
        }
        else{
            return NULL;
        }
    }

    /**
    * 描述：返回完整的数据库及表名字符串。
    * @param  string $tableName 必选 数据表名。
    * @return string
    */
    public function GetRow($sql,$arguments=array()){
        $pdoStatement=$this->Prepare($sql.' LIMIT 0,1');
        $this->BoundParameters($pdoStatement,$arguments);
        $data=$this->ExecuteHandler($pdoStatement,'fetch');
        if($data){
            return $data[0];
        }
        else{
            return NULL;
        }
    }

    /**
    * 描述：返回完整的数据库及表名字符串。
    * @param  string $tableName 必选 数据表名。
    * @return string
    */
    public function GetAll($sql,$arguments=array()){
        $pdoStatement=$this->Prepare($sql);
        $this->BoundParameters($pdoStatement,$arguments);
        return $this->ExecuteHandler($pdoStatement,'fetch');
    }

    /**
    * 描述：查询数据，可选绑定参数、可先分页。
    * @param string $sql        必选 SQL语句。
    * @param int    $pagination 必选 页码。
    * @param int    $size       必选 每页显示数。
    * @param array  $arguments  可选 SQL语句参数。
    * @return array(rows,meta)
    */
    public function GetLimit($sql,$pagination,$size,$arguments=array()){
        $pagination=(int)$pagination;
        $size=(int)$size;
        $isParameterized=(count($arguments)>0);
        #获取该SQL查询共获取行数。
        $sqlCount='SELECT COUNT(*) '.substr($sql,strpos(strtoupper($sql),'FROM'),strlen($sql));
        $pdoStatementCount=$this->Prepare($sqlCount);
        if($isParameterized){
            $this->BoundParameters($pdoStatementCount,$arguments);
        }
        $pdoStatementCount->Execute();
        $resultTemp=$pdoStatementCount->Fetch();
        $rowCount=$resultTemp['COUNT(*)'];
        #计算当前页码。
        if($pagination<1){
            $pagination=1;
        }
        if($size<1){
            $size=1;
        }
        #计算分页总数。
        $pageCount=$rowCount/$size;
        if($pageCount<1){
            $pageCount=1;
        }
        else{
            $pageCount=(int)ceil($pageCount);
        }
        #限制。
        if($pagination>$pageCount){
            $pagination=$pageCount;
        }
        $limitStart=(($pagination-1)*$size);
        #获取数据行
        $pdoStatement=$this->Prepare($sql.' LIMIT '.$limitStart.','.$size);
        if($isParameterized){
            $this->BoundParameters($pdoStatement,$arguments);
        }
        $result=array(
            'Meta'=>array(
                'Pagination'=>$pagination,
                'PageSize'=>$size,
                'RowCount'=>$rowCount,
                'PageCount'=>$pageCount,
            ),
            'Rows'=>$this->ExecuteHandler($pdoStatement,'fetch')
        );
        return $result;
    }

    /**
    * 描述：向表内插入数据，成功返回TRUE，失败返回FALSE。
    * @param  string $tableName 必选 数据表名。
    * @param  array  $data      必选 键值对左部为字段名，右部为值。
    * @return bool
    */
    public function Insert($tableName,$data){
        $fieldNameArray=array_keys($data);
        $fileValueArray=array();
        foreach($fieldNameArray as &$fileName){
            $fileValueArray[$fileName]=':'.$fileName;
            $fileName=$this->BoundModifier($fileName);
        }
        $sql=sprintf('INSERT INTO %s(%s)VALUES(%s)',$tableName,join(',',$fieldNameArray),implode(',',$fileValueArray));
        $pdoStatement=$this->Prepare($sql);
        $this->BoundParameters($pdoStatement,$data);
        return $this->ExecuteHandler($pdoStatement,'boolean'); //注：PDO自带lastInsertId方法。
    }

    /**
    * 描述：根据单个字段值修改数据,成功返回修改的行数（包括0行），失败返回FALSE。
    * @param  string $tableName  必选 数据表名。
    * @param  string $fieldName  必选 字段名。
    * @param  mix    $fieldValue 必选 字段值。
    * @param  array  $data       必选 键值对，左部为字段名，右部为值。
    * @return bool
    */
    public function Update($tableName,$data,$condition){
        $sql='UPDATE '.$tableName.' SET %s WHERE %s';
        //~
        if(is_array($condition)){
            $sqlScrapWhere=isset($condition['sql']) ? $condition['sql']:$condition[0];
            $conditionParameters=isset($condition['parameters']) ? $condition['parameters']:$condition[1];
        }
        else{
            $sqlScrapWhere=$condition;
            $conditionParameters=array();
        }
        $parameters=array_merge(array_values($data),$conditionParameters);
        //~
        $temp=NULL;
        foreach(array_keys($data) as $name){
            $temp.=sprintf('%s=?,',$name,$name);
        }
        $sqlScrapFields=substr($temp,0,strlen($temp)-1);
        //~
        $pdoStatement=$this->Prepare(sprintf($sql,$sqlScrapFields,$sqlScrapWhere));
        $this->BoundParameters($pdoStatement,$parameters);
        return $this->ExecuteHandler($pdoStatement,'boolean');
    }

    /**
    * 描述：根据单个字段值匹配删除数据,成功返回删除的行数（包括0行），失败返回FALSE。
    * @param  string      $tableName  必选 数据表名。
    * @param  string      $fieldName  必选 字段名。
    * @param  mix         $fieldValue 必选 字段值。
    * @return bool|number
    */
    public function Delete($tableName,$condition){
        $pdoStatement=$this->Prepare(sprintf('DELETE FROM %s WHERE %s=:%s',$this->Table($tableName),$fieldName,$fieldName));
        $argument=array($fieldName=>$fieldValue);
        $this->BoundParameters($pdoStatement,$argument);
        return $this->ExecuteHandler($pdoStatement,'rowCount');
    }

    /**
    * 描述：参数化执行SQL请求。
    * @param  string $sql       必选 SQL语句。
    * @param  string $arguments 必选 查询参数。
    * @return array|NULL|FALSE
    */
    public function ExecParameterized($sql,$arguments){
        $pdoStatement=$this->Prepare($sql);
        $this->BoundParameters($pdoStatement,$arguments);
        return $this->ExecuteHandler($pdoStatement,'bool');
    }

    /**
    * 描述：返回完整的数据库及表名字符串。
    * @param  string $tableName 必选 数据表名。
    * @return string
    */
    public function Table($tableName){
        if(!is_null($this->ExtentAttribute['TablePrefix'])&&str_replace(' ','',$this->ExtentAttribute['TablePrefix'])!==''){
            $tableName=$this->ExtentAttribute['TablePrefix'].'_'.$tableName;
        }
        return  $this->BoundModifier($this->DatabaseName).'.'.$this->BoundModifier($tableName);
    }

    /**
    * 描述：绑定关键字前后缀。
    * @param  string 必选 $value 需绑定的字符。
    * @return string
    */
    public function BoundModifier($value){
        if(isset($this->ExtentAttribute['Modifier']['Prefix'])){
            $value=$this->ExtentAttribute['Modifier']['Prefix'].$value;
        }
        if(isset($this->ExtentAttribute['Modifier']['Suffix'])){
            $value=$value.$this->ExtentAttribute['Modifier']['Suffix'];
        }
        return $value;
    }

    /**
    * 描述：返回使用使用EPDO扩展方法的错误，供调用和错误处理使用。
    * @param  string 必选 $value 需绑定的字符。
    * @return string
    */
    public function GetExtensionMethodErrorInfo(){
        return $this->ExtensionMethodErrorInfo;
    }

    /**
    * 描述：解析并生成WHERE语句的数组。
    * @param  array  $condition 必选 条件数组。
    * @return string
    */
    private function ParseCondition($condition,$statement=NULL,$parameters=array()){
        foreach($condition as $name=>$value){
            $sign=(substr($name,0,1)==='~' ? 'OR':'AND');
            $parameterName='condition_'.$name;
            $statement.=($name.'=:'.$parameterName.' '.$sign.' ');
            $parameters[$parameterName]=$value;
        }
        return array('sql'=>substr($statement,0,strlen($statement)-4),'parameters'=>$parameters);
    }

    /**
    * 描述：SQL请求失败时的错误处理函数。
    * @param  string 必选 $object 出错的PDO或PDOStatement对象。
    * @return void
    */
    private function ErrorHandler(&$object){
        if($object->errorCode()!='00000'){
            error_log(implode('---',$object->errorInfo()));
            $this->ExtensionMethodErrorInfo=implode('<br/>',$object->errorInfo());
        }
    }

    /**
    * 描述：EPDO的私有辅助方法，在参数化查询时，为PDOStatement对象绑定参数。
    * @param  pdostatement 必选 $pdoStatement       PDOStatement对象。
    * @param  array        必选 $arguments 参数数组，可以是关联数组，也可以是普通数组，将采用不同的方式处理。
    * @return void
    */
    private function BoundParameters(&$pdoStatement,&$arguments){
        $isAssoc=array_keys($arguments)!==range(0,count($arguments)-1);
        $values=array();
        if($isAssoc){
            foreach($arguments as $field=>$value){
                $isSpecial=(gettype($value)=='array') ? TRUE:FALSE;
                $values[$field]=$isSpecial ? $value[0]:$value;
                $type=$isSpecial ? (count($value)>=2 ? $value[1]:PDO::PARAM_STR):PDO::PARAM_STR;
                $length=$isSpecial ?(count($value)>=3 ? $value[2]:NULL ):NULL;
                $options=$isSpecial ? (count($value)>=4 ? $value[3]:NULL ):NULL;
                $pdoStatement->BindParam(':'.$field,$values[$field],$type,$length,$options);
            }
        }
        else{
            foreach($arguments as $key=>$value){
                $isSpecial=(gettype($value)=='array') ? TRUE:FALSE;
                $values[$key]=$isSpecial ? $value[0]:$value;
                $type=$isSpecial ? (count($value)>=2 ? $value[1]:PDO::PARAM_STR):PDO::PARAM_STR;
                $length=$isSpecial ?(count($value)>=3 ? $value[2]:NULL ):NULL;
                $options=$isSpecial ? (count($value)>=4 ? $value[3]:NULL ):NULL;
                $pdoStatement->BindParam($key+1,$values[$key],$type,$length,$options);
            }
        }
        unset($value);
    }

    /**
    * 描述：EPDO扩展方法逻辑返回值处理执行的结果。
    * @param  pdoStatement $object PDOStatement对象。
    * @return array|NULL|FALSE|0
    */
    private function ExecuteHandler(&$object,$type='fetch',$fetchType=PDO::FETCH_ASSOC) {
        $result=$object->Execute();
        if($result){
            if('fetch'===$type){ //bool,fetch,rowCount
                if(0===count($result)){
                    $result=NULL;
                }
                else{
                    $result=$object->FetchAll($fetchType);
                }
            }
            else{
                if('rowCount'===$type){
                    $result=$object->rowCount();
                }
                else{
                    $result=TRUE;
                }
            }
        }
        $this->ErrorHandler($object);
        return $result;
    }
}